/*
 * CSS display:block fits a <div> to its parent's width nicely, but we lack of a similar
 * thing in the case of height, that's why this script exists.
 * 
 * The script automatic resizes a <div> to fit to its parent either vertically or horizontally,
 * and auto adapts to window resize.
 * 
 * Usage: Include this JS and set attribute fitHeight="auto" or fitWidth="auto" to <div>.
 * 
 * 	e.g.	<div id="your-container" style="height:XXXX;">
 * 				<div id="your-header">...
 * 				<div id="your-content" fitHeight="auto">...
 * 				<div id="your-footer">...
 * 			</div>
 * 
 * Providing the container has a specified height, the height of "your-content" will be
 * adjusted automatically to take up all the vertical space left from header and footer.
 * Horizontal layout is also possible by floating child <div> to left and setting "fitWidth".
 * 
 * The script also checks the use of a strict DOCTYPE, which is very important for 
 * consistent positioning cross browsers, for details see - http://hsivonen.iki.fi/doctype/
 * 
 * =======================================================
 * 
 * SOME LESSONS LEARNT DURING DEVELOPING THIS SCRIPT:
 * 
 *  - Set all margins to 0!
 *  	- Margin may or may not overlap with each other, it depends on browser.
 *  	- With margin, different browser will give different values of offsetHeight/offsetWidth
 *  	  which is the corner stone of all position calculations.
 *  	- On contrary, padding works very consistent, recommend use padding always.
 *  
 *  - Avoid setting width to 100%, default block display works just fine
 *  	- Browsers treat 100% differently, some includes border/padding/scroll, some not.
 *  	- If you have to, make sure border/padding/scroll are all 0.
 *  	- For more advanced layout, you can use this script. :-)
 *  
 *  - For scrolling element, always specify both width/height and position:relative
 *  	- Required by IE 6/7 to behave properly in complex layout
 *
 *  - Use px to set padding/border, use em to specify width/height
 *
 *  - Better create a top container under <body> to hold your page contents
 *  	- Scripts often append new elements to <body> at runtime
 *  	- These unexpected new elements could break your layout
 *  	- It's better to have your content clearly separated
 *  
 *  - Layout using <div> only, other tags like <ul>, <li> all has some specialty here or there.
 *  
 *  - Careful setting width in 'em' or '%' for float:left layout in Firefox!!
 *  	- The most right block could be missing when you try to fill up the container width.
 *  	- Better use 'px' or leave some blank buffer if you have to use 'em' or '%'
 *  
 *  - Avoid setting width/height together with border/padding
 *  	- According to DOM standard, width/height does not include border/padding
 *  	- Setting both at the same time will complicate the position calculation
 *  	- If you have to do so, consult _getStylePixel(), it may be helpful
 *  
 * @author Li Yang (young4chong@gmail.com)
 * @dependency OPTIONAL: yevent.js 
 */

window.Yfit = window.Yfit || (function() {
	var self = {}; self.prototype = {};
	
	var isIE = !(document.dispatchEvent);
	
	function _observe(elem, event, func, capture) {
		if (elem.attachEvent)
			elem.attachEvent('on'+event, func);
		else
			elem.addEventListener(event, func, capture?true:false);
	}

	function _init() {
		// We're in onload event processing, setTimeout appends a timer event to the
		// end of the event queue, which is after all the onload processing is done.
		// The resize result is more cross-browser consistent in this way.
		setTimeout(_onResize, 1);
		
		// listen to resize of browser window
		_observe(window, "resize", _onResize);
		
		// listen to custom DOM change
		if (window.Yevent) {
			Yevent.observeCustom(document, "DomCreated", _onResize);
			Yevent.observeCustom(document, "DomUpdated", _onResize);
			Yevent.observeCustom(document, "Resize", _onResize);
		}
	}
	_observe(window, 'load', _init);
	
	/*
	 * Adjust <div>'s size of given target element and its descendants.
	 */
	function _onResize(evt) {
		// ignore event fired by myself
		if (evt && evt.originator===self) return;
		
		var root = evt ? (evt.target || evt.srcElement || document) : document;
		self.makeFit(root);
	}
	
	/*
	 * style - camelized style name, like 'paddingTop'
	 */
	function _getStyle(elem, style) {
		var value = elem.style[style];
		if (!value || value == 'auto') {
			if (elem.currentStyle) {
				value = elem.currentStyle[style];
			} else if (elem.ownerDocument.defaultView) {
				var css = elem.ownerDocument.defaultView.getComputedStyle(elem, null);
				value = css ? css[style] : null;
			}
		}
		if (style==='overflow')
			return value;
		else
			return value=='auto' ? null : value;
	}
	
	function _getStylePixel(elem, style) {
		var value = _getStyle(elem, style);
		if (value)
			if (/^[1-9].*(pt|em|%)/.test(value)) {
				alert('_getStylePixel() cannot tell pixel of '+value);
				value = 0;
			}
			else
				value = parseInt(value);
		return value || 0;
	}
	
	var _HorW = ['height', 'width'];
	var _offsetHorW = ['offsetHeight', 'offsetWidth'];
	
	function _getPaddingSize(elem, horw) {
		if (horw)
			return _getStylePixel(elem, 'paddingLeft')+_getStylePixel(elem, 'paddingRight');
		else
			return _getStylePixel(elem, 'paddingTop')+_getStylePixel(elem, 'paddingBottom');
	}

	function _getBorderSize(elem, horw) {
		if (horw)
			return _getStylePixel(elem, 'borderLeftWidth')+_getStylePixel(elem, 'borderRightWidth');
		else
			return _getStylePixel(elem, 'borderTopWidth')+_getStylePixel(elem, 'borderBottomWidth');
	}
	
	function _getPureSize(elem, horw) {
		return elem[_offsetHorW[horw]] - _getPaddingSize(elem, horw) - _getBorderSize(elem, horw);
	}
	
	/*
	 * fitCmd - like 'auto', or 'auto-70', or any script
	 */
	function _makeTheFitSize(elem, fitCmd, horw) {
		var size;
		
		if (fitCmd === 'auto' || fitCmd.indexOf('auto' <= 0)) { // a shortcut
			size = _getTheFitSize(elem, horw);
		} else {
			fitCmd = fitCmd.replace(/auto/g, _getTheFitSize(elem));
			try {
				size = eval(fitCmd);
			} catch (e) {
				alert("_makeTheFitSize() '"+fitCmd+"' error: "+e);
			}
		}
		
		size = (size < 0 ? 0 : size);
		var old = parseInt(elem.style[_HorW[horw]]);
		if (old == size) return;
		
		if (horw && size>0)
			size--; // fix the 1px error of float:left layout in FF
		
		// set the size
		elem.style[_HorW[horw]] = size+'px';
		// to force IE redraw!
		if (isIE && elem.style.position=='relative' && elem.style.overflow=='auto') {
			elem.style.display = 'none';
			elem.style.display = 'block';
		}
		
		/*
		 * Should fire "Resize"? Seems no one listens to "Resize" except yfit.js
		 */
		//if (window.Yevent) Yevent.fireCustom(elem, "Resize", {originator: self});
	}
	
	/*
	 * Calculate e's height/width that fits it to its parent, which means
	 * the parent's height/width minus all e's brothers.
	 */
	function _getTheFitSize(e, horw) {
		var p = e.parentNode; // parent element

		if (!e.fit_inited) {
			// remember border and padding to minus
			e.fit_minus = _getPaddingSize(p, horw) + _getBorderSize(p, horw) + _getPaddingSize(e, horw) + _getBorderSize(e, horw);
			
			// double check styles of margin/scrolling
			p.yfit_orig_overflow = _getStyle(p, 'overflow');
			p.style.overflow = 'hidden';
			for (var i = p.firstChild; i; i = i.nextSibling) {
				if (i.nodeType===1 && i.tagName!=='SCRIPT' && i.tagName!=='STYLE'
					&& 'absolute'!==_getStyle(i, 'position') && 'none'!==_getStyle(i, 'display')) {

					// no margin to all children
					i.style.margin = '0';
					
					// ensure fixed box for child with specified height/width
					// NOTICE: IE displays crap if overflow:hidden is set on an unspecified node,
					//         thanks god _getStyle() returns null for unspecified node in IE;
					//         FF works well with any overflow values on an unspecified node,
					//         _getStyle() in FF always returns something, even for unspecified node 
					var spec = _getStyle(i, _HorW[horw]);
					if (spec || i == e) {
						var overflow = _getStyle(i, 'overflow');
						if (overflow == 'visible') {
							if (i == e && 'hidden' != overflow) {
								i.style.position = 'relative'; // helps fixing some scrolling error in IE
								i.style.overflow = 'auto';
							} else
								i.style.overflow = i.style.overflow || 'hidden';
						}
					}
				}
			}
			
			e.fit_inited = true;
		}

		var pOtherSize = _getPureSize(p, 1-horw);
		var minus = 0;
		for (var i = p.firstChild; i; i = i.nextSibling)
			if (i.nodeType===1 && i.tagName!=='SCRIPT' && i.tagName!=='STYLE'
				&& 'absolute'!==_getStyle(i, 'position') && 'none'!==_getStyle(i, 'display')) {
				
				// specify both w/h if it is a float:left layout
				if (horw)
					i.style[_HorW[1-horw]] = (pOtherSize-_getBorderSize(i, 1-horw)-_getPaddingSize(i, 1-horw))+'px';
				
				if (i != e )
					minus += i[_offsetHorW[horw]];
			}
		
		return p[_offsetHorW[horw]] - minus - e.fit_minus;
	}
	
	function _makeFitOne() {
		var p = this, tmp, noop = true;
		for (var c = p.firstChild; c; c = c.nextSibling) {
			if (c.nodeType===1) {
				if (tmp = c.getAttribute('fitWidth')) {
					_makeTheFitSize(c, tmp, 1);
					noop = false;
				}
				else if (tmp = c.getAttribute('fitHeight')) {
					_makeTheFitSize(c, tmp, 0);
					noop = false;
				}
			}
		}
		
		// No-operation, the element was fit before, but not any more
		if (noop) {
			p.fitSize = null;
			p.style.overflow = p.yfit_orig_overflow || 'visible';
		}
	}
	
	/*
	 * Adjust <div>'s size of given root element and its descendants.
	 */
	self.makeFit = function(root) {
		// start from body should be enough
		if (root===document || root===window || root===document.documentElement)
			root = document.body;
		
		// returned divs does not contain root
		var div = root.getElementsByTagName("div"), n;
		
		try {
			// phase 1, bind fitSize()
			for (var i = -1; i < div.length; i++) {
				n = i < 0 ? root : div[i];
				if (!n.parentNode.fitSize && (n.getAttribute('fitHeight') || n.getAttribute('fitWidth')))
					n.parentNode.fitSize = _makeFitOne;
			}
			
			// phase 2, call fitSize() in top-down sequence
			for (var i = -1; i < div.length; i++) {
				n = i < 0 ? root : div[i];
				if (n.fitSize)
					n.fitSize();
			}
		} catch (err) {
			alert("Yfit.makeFit() error:" + err);
			throw err;
		}
	}
	
	/*
	 * Get element's height in pixel excluding border and padding
	 */
	self.getPureHeight = function(elem) {
		return _getPureSize(elem, 0);
	}
	
	/*
	 * Get element's width in pixel excluding border and padding
	 */
	self.getPureWidth = function(elem) {
		return _getPureSize(elem, 1);
	}
	
	/*
	 * if horw = 0, same to getPureHeight(); if horw = 1, same to getPureWidth();
	 */
	self.getPureSize = function(elem, horw) {
		return _getPureSize(elem, horw);
	}
	
	/*
	 * Get element's padding height (sum of top and bottom) in pixel
	 */
	self.getPaddingHeight = function(elem) {
		return _getPaddingSize(elem, 0);
	}
	
	/*
	 * Get element's padding width (sum of left and right) in pixel
	 */
	self.getPaddingWidth = function(elem) {
		return _getPaddingSize(elem, 1);
	}
	
	/*
	 * if horw = 0, same to getPaddingHeight(); if horw = 1, same to getPaddingWidth();
	 */
	self.getPaddingSize = function(elem, horw) {
		return _getPaddingSize(elem, horw);
	}
	
	/*
	 * Get element's border height (sum of top and bottom) in pixel
	 */
	self.getBorderHeight = function(elem) {
		return _getBorderSize(elem, 0);
	}
	
	/*
	 * Get element's border width (sum of left and right) in pixel
	 */
	self.getBorderWidth = function(elem) {
		return _getBorderSize(elem, 1);
	}
	
	/*
	 * if horw = 0, same to getBorderHeight(); if horw = 1, same to getBorderWidth();
	 */
	self.getBorderSize = function(elem, horw) {
		return _getBorderSize(elem, horw);
	}
	
	
	return self;
})();

function getDocType(doc) {
	var d = doc || document;
	for (var n = d.firstChild; n; n = n.nextSibling) {
		
		if (n.nodeType == 10) { // DOCTYPE node, for FF
			return "<!DOCTYPE "+n.name+" "+(n.publicId?'PUBLIC "'+n.publicId+'"':'')+" "+(n.systemId?'"'+n.systemId+'"':'')+">";
			
		} else if (n.nodeType == 8) { // comment node, for IE
			if (n.tagName === "!" && n.text.indexOf("DOCTYPE") >= 0)
				return n.text;
		}
	}
	return null; // failed
}

// check this document renders in standard mode
var doctype = getDocType();
if (doctype.indexOf("strict.dtd") < 0)
	alert("The document does not render in a standard mode. Please check your DOCTYPE definition. See http://hsivonen.iki.fi/doctype/ for details.");

// some CSS for consistent cross-browser looking, be free to override
document.writeln(
"<style type='text/css'>"+

	// cancel all margins, different browser calculates margins very differently 
	"html, body, div, span, applet, object, iframe,"+
	"h1, h2, h3, h4, h5, h6, p, blockquote, pre,"+
	"a, abbr, acronym, address, big, cite, code,"+
	"del, dfn, em, font, img, ins, kbd, q, s, samp,"+
	"small, strike, strong, sub, sup, tt, var,"+
	"b, u, i, center,"+
	"dl, dt, dd, ol, ul, li,"+
	"fieldset, form, label, legend,"+
	"table, caption, tbody, tfoot, thead, tr, th, td {"+
	"	margin:0; padding:0;"+
	"}"+
	
	// basics
	"body	{ font-family:Arial; font-size:10pt; line-height:1.2; }"+
	"p		{ padding:4px; }"+
	
	// some helpers
	".nowrap	{white-space:nowrap;}"+
	"span.inline-block, a.inline-block"+
	"			{ display:-moz-inline-stack; display:inline-block; }"+

"</style>"
);
